package laravel.mvc.model;

import entity_model.Entity;
import entity_model.Relationship;
import laravel.Laravel;
import utils.titles.Title;
import php.code.Variable;
import php.code.functions.Functions;
import php.code.routines.Constructor;
import php.code.routines.Method;
import php.code.statements.If;

import java.util.LinkedHashMap;
import java.util.Map;

import static entity_model.Relationship.Direction.BELONGS_TO;
import static entity_model.Relationship.Role.Cardinality.ONE;
import static php.code.Visibility.PUBLIC;
import static php.code.functions.TextLayout.MULTILINE;

public class Model extends AbstractModel {

    public static final Title TO_MAP_METHOD_TITLE = Title.fromLowerCamel("toMap");

    private Method morphable;

    public Model(Laravel laravel, Entity entity) {
        super(laravel, entity.getTitle().getSingularized(), entity);

        laravel.models.put(entity.getTitle(), this);

        if (entity.isAbstract())
            morphable = (Method) entityClass.createMethod(PUBLIC, getTitle().suffix("able"))
                    .returns("$this->morphTo()")
                    .doc("Define an polymorphic, inverse one-to-one relationship");

        if (getBaseModel() != null && getBaseModel().getSuperModel() == null)
            getBaseModel().getSuperModelProperty().addListener((observable, oldValue, newValue) -> createConstructor());
        else
            createConstructor();

        createToMapMethod();
    }

    private void createConstructor() {
        Variable attributes = new Variable("attributes");

        Constructor constructor = (Constructor) entityClass.createConstructor(PUBLIC, attributes.declare("array()"))
                .doc("Constructor");

//        if (getBaseModel() != null){ TODO
//            Model superModel = getBaseModel().getSuperModel();
//            Variable parent = constructor.declareVar(superModel.getTitle(), superModel.getConstructor().call()).getVariable();
//            constructor.addColumn(attributes.index("id", parent.getProperty("id")));
//        }

        constructor.callParent();

        for(Relationship.Related related : entity.getRelateds()) {
            if (related.getDirection() == BELONGS_TO)
                if (related.getTheOthersRoleCardinality() == ONE) {
                    Title attributeTitle = related.getTheOthersRoleTitle();
                    String attributeName = attributeTitle.toSnakeCase();
                    try {
                        if (Character.isDigit(attributeName.charAt(0)))
                            attributeName = "_" + attributeName;
                    }
                    catch (Exception e){
                        System.out.println(getName());
                        System.out.println(related);
                        throw e;
                    }

                    String attribute = String.format("$attributes['%s']", attributeName);

                    If anIf = new If(Functions.isset(attribute));

                    anIf.getConsequence().createLiteral(
                            String.format("$this->%s()->associate(%s);",
                                    attributeTitle.toLowerCamel(),
                                    attribute)
                    );
                    constructor.add(anIf);
                }
        }
    }

    private void createToMapMethod() {
        Map<String, String> map = new LinkedHashMap<>();

        for (String column : getFillable())
            map.put(column, String.format("$this->%s", Character.isDigit(column.charAt(0)) ? "_" + column : column));

        entityClass.createMethod(PUBLIC, TO_MAP_METHOD_TITLE)
                .returns(Functions.array(MULTILINE, map))
                .doc("Convert model to map (associative array) for json");
    }

    public Method getMorphable() {
        return morphable;
    }

}
